package com.study.Multithreading;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.Date;
import java.util.concurrent.*;

/**
 * 多线程测试
 *
 * @author yangyanbin
 * @date 2022/12/06
 * @since 20221206
 */
@Slf4j
public class MultithreadingTest {

    /**
     * 一、创建异步任务
     * 1. supplyAsync
     * supplyAsync是创建带有返回值的异步任务。它有如下两个方法，一个是使用默认线程池（ForkJoinPool.commonPool()）的方法，一个是带有自定义线程池的重载方法
     */

    /**
     * 默认线程池(有返回值的)
     */
    @Test
    public void defaultExecutor() {
        Date date = new Date();
        long startTime = date.getTime();
        System.out.println(startTime);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("i = " + i);
            }
            System.out.println("带返回值异步请求，默认线程池");
            return "result";
        });

        try {
            System.out.println("结果->" + supplyAsync.get());
            System.out.println("接口耗时:" + (System.currentTimeMillis() - startTime));


        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            log.error("执行错误！", e);
        }
    }

    /**
     * 自定义线程池（有返回值）
     */
    @Test
    public void divExecutor() {
        // 不建议使用Executors创建线程池最大线程数会导致OOM
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println("do something....");
            return "result";
        }, executorService);

        // 等待子任务执行完成
        try {
            System.out.println("结果->" + supplyAsync.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            log.error("执行错误！", e);
        }

    }


    /**
     * 2. runAsync
     * runAsync是创建没有返回值的异步任务。它有如下两个方法，一个是使用默认线程池（ForkJoinPool.commonPool()）的方法，一个是带有自定义线程池的重载方法
     */

    /**
     * 测试运行异步使用默认遗嘱执行人
     *
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @Test
    public void testRunAsyncUseDefaultExecutor() throws ExecutionException, InterruptedException {
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
            System.out.println("runAsync方法无返回值使用默认的线程池");

        });

        // 等待任务执行完成
        System.out.println("结果->" + runAsync.get());
    }

    /**
     * 测试运行异步使用div执行人
     *
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @Test
    public void testRunAsyncUseDivExecutor() throws ExecutionException, InterruptedException {
        // 不建议使用Executors创建线程池最大线程数会导致OOM
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
            System.out.println("runAsync方法无返回值使用自定义的线程池");
        }, executorService);

        // 等待任务执行完成
        System.out.println("结果->" + runAsync.get());

    }

    /**
     * 3、有返回值->获取任务结果的方法
     * 如果完成则返回结果，否则就抛出具体的异常
     * public T get() throws InterruptedException, ExecutionException
     *
     *  最大时间等待返回结果，否则就抛出具体异常
     * public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
     *
     * 完成时返回结果值，否则抛出unchecked异常。为了更好地符合通用函数形式的使用，如果完成此 CompletableFuture所涉及的计算引发异常，则此方法将引发unchecked异常并将底层异常作为其原因
     * public T join()
     *
     *  如果完成则返回结果值（或抛出任何遇到的异常），否则返回给定的 valueIfAbsent。
     * public T getNow(T valueIfAbsent)
     *
     *  如果任务没有完成，返回的值设置为给定值
     * public boolean complete(T value)
     *
     *  如果任务没有完成，就抛出给定异常
     * public boolean completeExceptionally(Throwable ex)
     *
     */

    /**
     * 默认线程池(有返回值的)
     */
    @Test
    public void defaultExecutorDivReturn() throws Exception {
        Date date = new Date();
        long startTime = date.getTime();
        System.out.println(startTime);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("i = " + i);
            }
            System.out.println("带返回值异步请求，默认线程池");
            return "result";
        });

        try {
            // 获取任务结果的方法(10ms无返回结果则抛异常)
            System.out.println("结果->" + supplyAsync.get(10, TimeUnit.MILLISECONDS));
            System.out.println("接口耗时:" + (System.currentTimeMillis() - startTime));


        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            log.error("执行错误！{}", JSON.toJSONString(e));
            throw e;
        }
    }


    /**
     * 二、异步回调处理
     *
     */


    /**
     * 异步回调
     * thenApply 表示某个任务执行完成后执行的动作，即回调方法，会将该任务的执行结果即方法返回值作为入参传递到回调方法中，带有返回值。
     */
    @Test
    public void testThenApply() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Integer> thenApply = supplyAsync.thenApply((result) -> {
            System.out.println(Thread.currentThread() + "将第一个异步方法的返值作为入参执行该方法");
            result += 2;
            return result;
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenApply结果->" + thenApply.get());


    }

    /**
     * thenApply和thenApplyAsync区别在于，使用thenApplyAsync方法时子任务与父任务使用的是同一个线程，
     * 而thenApply在子任务中是另起一个线程执行任务，并且thenApplyAsync可以自定义线程池，默认的使用ForkJoinPool.commonPool()线程池。
     */

    /**
     * 测试然后应用异步
     *
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @Test
    public void testThenApplyAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Integer> thenApplyAsync = supplyAsync.thenApplyAsync((result) -> {
            System.out.println(Thread.currentThread() + "将第一个异步方法的返值作为入参执行该方法");
            result += 2;
            return result;
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenApplyAsync结果->" + thenApplyAsync.get());


    }


    /**
     * thenAccept表示某个任务执行完成后执行的动作，即回调方法，会将该任务的执行结果即方法返回值作为入参传递到回调方法中，[无返回值]。
     * 在子任务中是另起一个线程执行任务
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */

    @Test
    public void testThenAccept() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Void> thenAccept = supplyAsync.thenAccept((result) -> {
            System.out.println(Thread.currentThread() + "将第一个异步方法的返值作为入参执行该方法");
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenAccept结果->" + thenAccept.get());

    }


    /**
     * thenAcceptAsync，即回调方法，会将该任务的执行结果即方法返回值作为入参传递到回调方法中，[无返回值]。
     * 使用thenAcceptAsync方法时子任务与父任务使用的是同一个线程
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testThenAcceptAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Void> thenAcceptAsync = supplyAsync.thenAcceptAsync((result) -> {
            System.out.println(Thread.currentThread() + "将第一个异步方法的返值作为入参执行该方法");
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenAcceptAsync结果->" + thenAcceptAsync.get());

    }


    /**
     * thenRun表示某个任务执行完成后执行的动作，即回调方法，无入参，无返回值。
     * 在子任务中是另起一个线程执行任务
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testThenRun() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Void> thenRun = supplyAsync.thenRun(() -> {
            System.out.println(Thread.currentThread() + "回调方法，无入参，无返回值");
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenRun-结果>" + thenRun.get());

    }

    /**
     * thenRunAsync表示某个任务执行完成后执行的动作，即回调方法，无入参，无返回值。
     * 使用thenRunAsync方法时子任务与父任务使用的是同一个线程
     */

    @Test
    public void testThenRunAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            return 1;
        });

        CompletableFuture<Void> thenRunAsync = supplyAsync.thenRunAsync(() -> {
            System.out.println(Thread.currentThread() + "回调方法，无入参，无返回值");
        });

        // 等待任务1执行完成
        System.out.println("supplyAsync结果->" + supplyAsync.get());


        // 等待任务2执行完成
        System.out.println("thenRunAsync-结果>" + thenRunAsync.get());

    }


    /**
     * whenComplete是当某个任务执行完成后执行的回调方法，会将执行结果或者执行期间抛出的异常传递给回调方法，如果是正常执行则异常为null，
     * 回调方法对应的CompletableFuture的result和该任务一致，如果该任务正常执行，则get方法返回执行结果，如果是执行异常，则get方法抛出异常。
     * <p>
     * <p>
     * <p>
     * whenCompleteAsync和whenComplete区别也是whenCompleteAsync可能会另起一个线程执行任务，
     * 并且thenRunAsync可以自定义线程池，默认的使用ForkJoinPool.commonPool()线程池。
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testWhenComplete() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            //int a = 1/0;
            return 1;
        });


        CompletableFuture<Integer> whenComplete = supplyAsync.whenComplete((result, e) -> {
            System.out.println("上个任务结果" + result);
            System.out.println("上个任务抛出的异常" + e);
            System.out.println(Thread.currentThread() + "回调方法，有入参(异常)，返回值");

        });

        System.out.println("supplyAsync结果->" + supplyAsync.get());

        System.out.println("whenComplete结果->" + whenComplete.get());

    }

    /***
     * handle和handleAsync
     * 跟whenComplete基本一致，区别在于handle的回调方法有返回值(需return)。
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testHandle() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            //int a = 1/0;
            return 1;
        });
        CompletableFuture<Integer> handle = supplyAsync.handle((result, e) -> {
            System.out.println("上个任务结果" + result);
            System.out.println("上个任务抛出的异常" + e);
            System.out.println(Thread.currentThread() + "回调方法，有入参(异常)，返回值");

            return result + 2;
        });

        // 等待任务2执行完成
        System.out.println("handle-结果>" + handle.get());

    }


    /**
     * 三、多任务组合处理
     */

    /**
     * 1.thenCombine、thenAcceptBoth 和runAfterBoth
     * 这三个方法都是将两个CompletableFuture组合起来处理，只有两个任务都正常完成时，才进行下阶段任务。
     * <p>
     * 区别：thenCombine会将两个任务的执行结果作为所提供函数的参数，且该方法有返回值；
     * thenAcceptBoth同样将两个任务的执行结果作为方法入参，但是无返回值；
     * runAfterBoth没有入参，也没有返回值。注意两个任务中只要有一个执行异常，则将该异常信息作为指定任务的执行结果。
     */

    @Test
    public void testThenCombine() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync1 = CompletableFuture.supplyAsync(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("i = " + i);
            }
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            //int a = 1/0;
            return 1;
        });


        CompletableFuture<Integer> supplyAsync2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第二个异步方法带返回值");
            int a = 1 / 0;
            return 2;
        });

        CompletableFuture<Integer> thenCombine = supplyAsync1.thenCombine(supplyAsync2, (a, b) -> {
            System.out.println(Thread.currentThread() + "将两个任务的执行结果作为所提供函数的参数，且该方法有返回值");
            return a + b;
        });

        System.out.println("thenCombine结果" + thenCombine.get());

    }


    /**
     * thenAcceptBoth同样将两个任务的执行结果作为方法入参，但是无返回值；
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testThenAcceptBoth() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            //int a = 1/0;
            return 1;
        });


        CompletableFuture<Integer> supplyAsync2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第二个异步方法带返回值");
            //int a = 1/0;
            return 2;
        });

        CompletableFuture<Void> thenAcceptBoth = supplyAsync1.thenAcceptBoth(supplyAsync2, (a, b) -> {
            System.out.println(Thread.currentThread() + "将两个任务的执行结果作为所提供函数的参数，且该方法有返回值");
            System.out.println(a + b);
        });

        System.out.println("thenAcceptBoth结果(获取不到返回值)" + thenAcceptBoth.get());

    }


    /**
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testRunAfterBoth() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第一个异步方法带返回值");
            //int a = 1/0;
            return 1;
        });


        CompletableFuture<Integer> supplyAsync2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + "创建第二个异步方法带返回值");
            //int a = 1/0;
            return 2;
        });

        CompletableFuture<Void> thenAcceptBoth = supplyAsync1.thenAcceptBoth(supplyAsync2, (a, b) -> {
            System.out.println(Thread.currentThread() + "将两个任务的执行结果作为所提供函数的参数，且该方法有返回值");
            System.out.println(a + b);
        });

        System.out.println("thenAcceptBoth结果(获取不到返回值)" + thenAcceptBoth.get());

    }


}
